/*
 * Copyright (c) 2007-2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: PredicatedNodeTest.java,v 1.2.4.2 2005/09/14 19:45:20 jeffsuttor Exp $
 */
package com.sun.org.apache.xpath.internal.axes;

import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMIterator;
import com.sun.org.apache.xml.internal.utils.PrefixResolver;
import com.sun.org.apache.xpath.internal.Expression;
import com.sun.org.apache.xpath.internal.ExpressionOwner;
import com.sun.org.apache.xpath.internal.XPathContext;
import com.sun.org.apache.xpath.internal.XPathVisitor;
import com.sun.org.apache.xpath.internal.compiler.Compiler;
import com.sun.org.apache.xpath.internal.objects.XObject;
import com.sun.org.apache.xpath.internal.patterns.NodeTest;

public abstract class PredicatedNodeTest extends NodeTest implements SubContextList {
    static final long serialVersionUID = -6193530757296377351L;

    /**
     * Construct an AxesWalker using a LocPathIterator.
     *
     * @param locPathIterator non-null reference to the parent iterator.
     */
    PredicatedNodeTest(LocPathIterator locPathIterator) {
        m_lpi = locPathIterator;
    }

    /**
     * Construct an AxesWalker.  The location path iterator will have to be set
     * before use.
     */
    PredicatedNodeTest() {
    }

    /**
     * Read the object from a serialization stream.
     *
     * @param stream Input stream to read from
     *
     * @throws java.io.IOException
     * @throws javax.xml.transform.TransformerException
     */
    private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, javax.xml.transform.TransformerException {
        try {
            stream.defaultReadObject();
            m_predicateIndex = -1;
            resetProximityPositions();
        } catch (ClassNotFoundException cnfe) {
            throw new javax.xml.transform.TransformerException(cnfe);
        }
    }

    /**
     * Get a cloned PrdicatedNodeTest.
     *
     * @return A new PredicatedNodeTest that can be used without mutating this one.
     *
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        // Do not access the location path itterator during this operation!

        PredicatedNodeTest clone = (PredicatedNodeTest) super.clone();

        if ((null != this.m_proximityPositions) && (this.m_proximityPositions == clone.m_proximityPositions)) {
            clone.m_proximityPositions = new int[this.m_proximityPositions.length];

            System.arraycopy(this.m_proximityPositions, 0, clone.m_proximityPositions, 0, this.m_proximityPositions.length);
        }

        if (clone.m_lpi == this)
            clone.m_lpi = (LocPathIterator) clone;

        return clone;
    }

    // Only for clones for findLastPos.  See bug4638.
    protected int m_predCount = -1;

    /**
     * Get the number of predicates that this walker has.
     *
     * @return the number of predicates that this walker has.
     */
    public int getPredicateCount() {
        if (-1 == m_predCount)
            return (null == m_predicates) ? 0 : m_predicates.length;
        else
            return m_predCount;
    }

    /**
     * Set the number of predicates that this walker has.  This does more
     * that one would think, as it creates a new predicate array of the
     * size of the count argument, and copies count predicates into the new
     * one from the old, and then reassigns the predicates value.  All this
     * to keep from having to have a predicate count value.
     *
     * @param count The number of predicates, which must be equal or less
     *               than the existing count.
     */
    public void setPredicateCount(int count) {
        if (count > 0) {
            Expression[] newPredicates = new Expression[count];
            for (int i = 0; i < count; i++) {
                newPredicates[i] = m_predicates[i];
            }
            m_predicates = newPredicates;
        } else
            m_predicates = null;

    }

    /**
     * Init predicate info.
     *
     * @param compiler The Compiler object that has information about this
     *                 walker in the op map.
     * @param opPos The op code position of this location step.
     *
     * @throws javax.xml.transform.TransformerException
     */
    protected void initPredicateInfo(Compiler compiler, int opPos) throws javax.xml.transform.TransformerException {

        int pos = compiler.getFirstPredicateOpPos(opPos);

        if (pos > 0) {
            m_predicates = compiler.getCompiledPredicates(pos);
            if (null != m_predicates) {
                for (int i = 0; i < m_predicates.length; i++) {
                    m_predicates[i].exprSetParent(this);
                }
            }
        }
    }

    /**
     * Get a predicate expression at the given index.
     *
     *
     * @param index Index of the predicate.
     *
     * @return A predicate expression.
     */
    public Expression getPredicate(int index) {
        return m_predicates[index];
    }

    /**
     * Get the current sub-context position.
     *
     * @return The node position of this walker in the sub-context node list.
     */
    public int getProximityPosition() {

        // System.out.println("getProximityPosition - m_predicateIndex: "+m_predicateIndex);
        return getProximityPosition(m_predicateIndex);
    }

    /**
     * Get the current sub-context position.
     *
     * @param xctxt The XPath runtime context.
     *
     * @return The node position of this walker in the sub-context node list.
     */
    public int getProximityPosition(XPathContext xctxt) {
        return getProximityPosition();
    }

    /**
     * Get the index of the last node that can be itterated to.
     *
     *
     * @param xctxt XPath runtime context.
     *
     * @return the index of the last node that can be itterated to.
     */
    public abstract int getLastPos(XPathContext xctxt);

    /**
     * Get the current sub-context position.
     *
     * @param predicateIndex The index of the predicate where the proximity
     *                       should be taken from.
     *
     * @return The node position of this walker in the sub-context node list.
     */
    protected int getProximityPosition(int predicateIndex) {
        return (predicateIndex >= 0) ? m_proximityPositions[predicateIndex] : 0;
    }

    /**
     * Reset the proximity positions counts.
     */
    public void resetProximityPositions() {
        int nPredicates = getPredicateCount();
        if (nPredicates > 0) {
            if (null == m_proximityPositions)
                m_proximityPositions = new int[nPredicates];

            for (int i = 0; i < nPredicates; i++) {
                try {
                    initProximityPosition(i);
                } catch (Exception e) {
                    // TODO: Fix this...
                    throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(e);
                }
            }
        }
    }

    /**
     * Init the proximity position to zero for a forward axes.
     *
     * @param i The index into the m_proximityPositions array.
     *
     * @throws javax.xml.transform.TransformerException
     */
    public void initProximityPosition(int i) throws javax.xml.transform.TransformerException {
        m_proximityPositions[i] = 0;
    }

    /**
     * Count forward one proximity position.
     *
     * @param i The index into the m_proximityPositions array, where the increment
     *          will occur.
     */
    protected void countProximityPosition(int i) {
        // Note that in the case of a UnionChildIterator, this may be a
        // static object and so m_proximityPositions may indeed be null!
        int[] pp = m_proximityPositions;
        if ((null != pp) && (i < pp.length))
            pp[i]++;
    }

    /**
     * Tells if this is a reverse axes.
     *
     * @return false, unless a derived class overrides.
     */
    public boolean isReverseAxes() {
        return false;
    }

    /**
     * Get which predicate is executing.
     *
     * @return The current predicate index, or -1 if no predicate is executing.
     */
    public int getPredicateIndex() {
        return m_predicateIndex;
    }

    /**
     * Process the predicates.
     *
     * @param context The current context node.
     * @param xctxt The XPath runtime context.
     *
     * @return the result of executing the predicate expressions.
     *
     * @throws javax.xml.transform.TransformerException
     */
    boolean executePredicates(int context, XPathContext xctxt) throws javax.xml.transform.TransformerException {

        int nPredicates = getPredicateCount();
        // System.out.println("nPredicates: "+nPredicates);
        if (nPredicates == 0)
            return true;

        PrefixResolver savedResolver = xctxt.getNamespaceContext();

        try {
            m_predicateIndex = 0;
            xctxt.pushSubContextList(this);
            xctxt.pushNamespaceContext(m_lpi.getPrefixResolver());
            xctxt.pushCurrentNode(context);

            for (int i = 0; i < nPredicates; i++) {
                // System.out.println("Executing predicate expression - waiting count: "+m_lpi.getWaitingCount());
                XObject pred = m_predicates[i].execute(xctxt);
                // System.out.println("\nBack from executing predicate expression - waiting count: "+m_lpi.getWaitingCount());
                // System.out.println("pred.getType(): "+pred.getType());
                if (XObject.CLASS_NUMBER == pred.getType()) {
                    if (DEBUG_PREDICATECOUNTING) {
                        System.out.flush();
                        System.out.println("\n===== start predicate count ========");
                        System.out.println("m_predicateIndex: " + m_predicateIndex);
                        // System.out.println("getProximityPosition(m_predicateIndex): "
                        //                   + getProximityPosition(m_predicateIndex));
                        System.out.println("pred.num(): " + pred.num());
                    }

                    int proxPos = this.getProximityPosition(m_predicateIndex);
                    int predIndex = (int) pred.num();
                    if (proxPos != predIndex) {
                        if (DEBUG_PREDICATECOUNTING) {
                            System.out.println("\nnode context: " + nodeToString(context));
                            System.out.println("index predicate is false: " + proxPos);
                            System.out.println("\n===== end predicate count ========");
                        }
                        return false;
                    } else if (DEBUG_PREDICATECOUNTING) {
                        System.out.println("\nnode context: " + nodeToString(context));
                        System.out.println("index predicate is true: " + proxPos);
                        System.out.println("\n===== end predicate count ========");
                    }

                    // If there is a proximity index that will not change during the
                    // course of itteration, then we know there can be no more true
                    // occurances of this predicate, so flag that we're done after
                    // this.
                    //
                    // bugzilla 14365
                    // We can't set m_foundLast = true unless we're sure that -all-
                    // remaining parameters are stable, or else last() fails. Fixed so
                    // only sets m_foundLast if on the last predicate
                    if (m_predicates[i].isStableNumber() && i == nPredicates - 1) {
                        m_foundLast = true;
                    }
                } else if (!pred.bool())
                    return false;

                countProximityPosition(++m_predicateIndex);
            }
        } finally {
            xctxt.popCurrentNode();
            xctxt.popNamespaceContext();
            xctxt.popSubContextList();
            m_predicateIndex = -1;
        }

        return true;
    }

    /**
     * This function is used to fixup variables from QNames to stack frame
     * indexes at stylesheet build time.
     * @param vars List of QNames that correspond to variables.  This list
     * should be searched backwards for the first qualified name that
     * corresponds to the variable reference qname.  The position of the
     * QName in the vector from the start of the vector will be its position
     * in the stack frame (but variables above the globalsTop value will need
     * to be offset to the current stack frame).
     */
    public void fixupVariables(java.util.Vector vars, int globalsSize) {
        super.fixupVariables(vars, globalsSize);

        int nPredicates = getPredicateCount();

        for (int i = 0; i < nPredicates; i++) {
            m_predicates[i].fixupVariables(vars, globalsSize);
        }
    }

    /**
     * Diagnostics.
     *
     * @param n Node to give diagnostic information about, or null.
     *
     * @return Informative string about the argument.
     */
    protected String nodeToString(int n) {
        if (DTM.NULL != n) {
            DTM dtm = m_lpi.getXPathContext().getDTM(n);
            return dtm.getNodeName(n) + "{" + (n + 1) + "}";
        } else {
            return "null";
        }
    }

    //=============== NodeFilter Implementation ===============

    /**
     *  Test whether a specified node is visible in the logical view of a
     * TreeWalker or NodeIterator. This function will be called by the
     * implementation of TreeWalker and NodeIterator; it is not intended to
     * be called directly from user code.
     * @param n  The node to check to see if it passes the filter or not.
     * @return  a constant to determine whether the node is accepted,
     *   rejected, or skipped, as defined  above .
     */
    public short acceptNode(int n) {

        XPathContext xctxt = m_lpi.getXPathContext();

        try {
            xctxt.pushCurrentNode(n);

            XObject score = execute(xctxt, n);

            // System.out.println("\n::acceptNode - score: "+score.num()+"::");
            if (score != NodeTest.SCORE_NONE) {
                if (getPredicateCount() > 0) {
                    countProximityPosition(0);

                    if (!executePredicates(n, xctxt))
                        return DTMIterator.FILTER_SKIP;
                }

                return DTMIterator.FILTER_ACCEPT;
            }
        } catch (javax.xml.transform.TransformerException se) {

            // TODO: Fix this.
            throw new RuntimeException(se.getMessage());
        } finally {
            xctxt.popCurrentNode();
        }

        return DTMIterator.FILTER_SKIP;
    }

    /**
     * Get the owning location path iterator.
     *
     * @return the owning location path iterator, which should not be null.
     */
    public LocPathIterator getLocPathIterator() {
        return m_lpi;
    }

    /**
     * Set the location path iterator owner for this walker.  Besides
     * initialization, this function is called during cloning operations.
     *
     * @param li non-null reference to the owning location path iterator.
     */
    public void setLocPathIterator(LocPathIterator li) {
        m_lpi = li;
        if (this != li)
            li.exprSetParent(this);
    }

    /**
     * Tell if this expression or it's subexpressions can traverse outside
     * the current subtree.
     *
     * @return true if traversal outside the context node's subtree can occur.
     */
    public boolean canTraverseOutsideSubtree() {
        int n = getPredicateCount();
        for (int i = 0; i < n; i++) {
            if (getPredicate(i).canTraverseOutsideSubtree())
                return true;
        }
        return false;
    }

    /**
     * This will traverse the heararchy, calling the visitor for
     * each member.  If the called visitor method returns
     * false, the subtree should not be called.
     *
     * @param visitor The visitor whose appropriate method will be called.
     */
    public void callPredicateVisitors(XPathVisitor visitor) {
        if (null != m_predicates) {
            int n = m_predicates.length;
            for (int i = 0; i < n; i++) {
                ExpressionOwner predOwner = new PredOwner(i);
                if (visitor.visitPredicate(predOwner, m_predicates[i])) {
                    m_predicates[i].callVisitors(predOwner, visitor);
                }

            }
        }
    }

    /**
     * @see Expression#deepEquals(Expression)
     */
    public boolean deepEquals(Expression expr) {
        if (!super.deepEquals(expr))
            return false;

        PredicatedNodeTest pnt = (PredicatedNodeTest) expr;
        if (null != m_predicates) {

            int n = m_predicates.length;
            if ((null == pnt.m_predicates) || (pnt.m_predicates.length != n))
                return false;
            for (int i = 0; i < n; i++) {
                if (!m_predicates[i].deepEquals(pnt.m_predicates[i]))
                    return false;
            }
        } else if (null != pnt.m_predicates)
            return false;

        return true;
    }

    /** This is true if nextNode returns null. */
    transient protected boolean m_foundLast = false;

    /** The owning location path iterator.
     *  @serial */
    protected LocPathIterator m_lpi;

    /**
     * Which predicate we are executing.
     */
    transient int m_predicateIndex = -1;

    /** The list of predicate expressions. Is static and does not need
     *  to be deep cloned.
     *  @serial
     */
    private Expression[] m_predicates;

    /**
     * An array of counts that correspond to the number
     * of predicates the step contains.
     */
    transient protected int[] m_proximityPositions;

    /** If true, diagnostic messages about predicate execution will be posted.  */
    static final boolean DEBUG_PREDICATECOUNTING = false;

    class PredOwner implements ExpressionOwner {
        int m_index;

        PredOwner(int index) {
            m_index = index;
        }

        /**
         * @see ExpressionOwner#getExpression()
         */
        public Expression getExpression() {
            return m_predicates[m_index];
        }

        /**
         * @see ExpressionOwner#setExpression(Expression)
         */
        public void setExpression(Expression exp) {
            exp.exprSetParent(PredicatedNodeTest.this);
            m_predicates[m_index] = exp;
        }
    }

}
